﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;

namespace SocialKit.LightRest.OAuth
{
    /// <summary>
    /// Provides methods and signature method property used to compute OAuth signature string.
    /// </summary>
    public interface ISignatureProvider
    {
        /// <summary>
        /// Computes the OAuth signature string.
        /// </summary>
        /// <param name="signatureBaseString">The base string to compute.</param>
        /// <param name="consumerSecret">The consumer secret.</param>
        /// <param name="tokenSecret">The token secret.</param>
        /// <returns></returns>
        string ComputeSignature(string signatureBaseString, string consumerSecret, string tokenSecret);

        /// <summary>
        /// Gets the signature method used to compute signature.
        /// </summary>
        string SignatureMethod { get; }
    }

    /// <summary>
    /// Represents the PLAINTEXT singature provider of OAuth.
    /// </summary>
    public class PlainTextSignatureProvider : ISignatureProvider
    {
        /// <summary>
        /// Initialize a new instance of PlainTextSignatureProvider class.
        /// </summary>
        public PlainTextSignatureProvider() { }

        /// <summary>
        /// Computes the signature string use PLAINTEXT method of OAuth.
        /// </summary>
        /// <param name="signatureBaseString">Not need the base string when use PLAINTEXT method.</param>
        /// <param name="consumerSecret">The consumer secret.</param>
        /// <param name="tokenSecret">The token secret.</param>
        /// <returns></returns>
        public string ComputeSignature(string signatureBaseString, string consumerSecret, string tokenSecret)
        {
            if (string.IsNullOrEmpty(consumerSecret))
                throw new ArgumentNullException("consumerSecret");

            if (tokenSecret == null)
                tokenSecret = string.Empty;

            return RestUtility.UrlEncode(string.Format("{0}&{1}", consumerSecret, tokenSecret));
        }

        /// <summary>
        /// Gets the signature method name, always returns the "PLAINTEXT" string.
        /// </summary>
        public string SignatureMethod
        {
            get { return OAuthStrings.PLAINTEXTSignatureMethod; }
        }
    }

    /// <summary>
    /// Represents the HMACSHA1 singature provider of OAuth.
    /// </summary>
    public class HMACSHA1SignatureProvider : ISignatureProvider
    {
        /// <summary>
        /// Initialize a new instance of HMACSHA1SignatureProvider class.
        /// </summary>
        public HMACSHA1SignatureProvider() { }

        /// <summary>
        /// Computes the signature string use HMACSHA1 method of OAuth.
        /// </summary>
        /// <param name="signatureBaseString">The base string for computing signature.</param>
        /// <param name="consumerSecret">The consumer secret.</param>
        /// <param name="tokenSecret">The token secret.</param>
        /// <returns></returns>
        public string ComputeSignature(string signatureBaseString, string consumerSecret, string tokenSecret)
        {
            if (string.IsNullOrEmpty(signatureBaseString))
                throw new ArgumentNullException("signatureBaseString");

            if (string.IsNullOrEmpty(consumerSecret))
                throw new ArgumentNullException("consumerSecret");

            if (tokenSecret == null)
                tokenSecret = string.Empty;

            var key = Encoding.ASCII.GetBytes(string.Format("{0}&{1}", consumerSecret, tokenSecret));
            var hash = new HMACSHA1(key);

            return Convert.ToBase64String(hash.ComputeHash(Encoding.ASCII.GetBytes(signatureBaseString)));
        }

        /// <summary>
        /// Gets the signature method name, always returns the "HMAC-SHA1" string.
        /// </summary>
        public string SignatureMethod
        {
            get { return OAuthStrings.HMACSHA1SignatureMethod; }
        }
    }

#if !PocketPC

    /// <summary>
    /// Represents the RSASHA1 singature provider of OAuth.
    /// </summary>
    public class RSASHA1SignatureProvider : ISignatureProvider
    {
        string pfxFile;

        /// <summary>
        /// Gets or sets the PFX private key file path.
        /// </summary>
        public string PfxFile
        {
            get { return pfxFile; }
            set
            {
                if (string.IsNullOrEmpty(value))
                    throw new ArgumentException("You must specify the full path of your Pfx file.");

                if (!File.Exists(value))
                    throw new FileNotFoundException("The Pfx file is not there.");

                pfxFile = value;
            }
        }

        /// <summary>
        /// Gets or sets the PFX password.
        /// </summary>
        public string PfxPassword { get; set; }

        X509Certificate2 certificate;

        /// <summary>
        /// Gets the X509 Certificate for computing signature of OAuth.
        /// </summary>
        public X509Certificate2 Certificate
        {
            get
            {
                if (certificate == null)
                {
                    if (PfxPassword == null)
                        PfxPassword = string.Empty;

                    certificate = new X509Certificate2(PfxFile, PfxPassword);
                }

                return certificate;
            }
        }

        /// <summary>
        /// Initialize a new instance of RSASHA1SignatureProvider class.
        /// </summary>
        public RSASHA1SignatureProvider() { }

        /// <summary>
        /// Initialize a new instance of RSASHA1SignatureProvider class and specify the pfx file and password.
        /// </summary>
        /// <param name="pfxFile">The full path of your pfx file.</param>
        /// <param name="pfxPassword">The password of your private key.</param>
        public RSASHA1SignatureProvider(string pfxFile, string pfxPassword)
        {
            this.PfxFile = pfxFile;
            this.PfxPassword = pfxPassword;
        }

        #region ISignatureProvider Members

        /// <summary>
        /// Computes the signature string use RSASHA1 method of OAuth.
        /// </summary>
        /// <param name="signatureBaseString">The base string for computing signature.</param>
        /// <param name="consumerSecret">The consumer secret.</param>
        /// <param name="tokenSecret">The token secret.</param>
        /// <returns></returns>
        public string ComputeSignature(string signatureBaseString, string consumerSecret, string tokenSecret)
        {
            if (string.IsNullOrEmpty(signatureBaseString))
                throw new ArgumentNullException("signatureBaseString");

            if (string.IsNullOrEmpty(consumerSecret))
                throw new ArgumentNullException("consumerSecret");

            if (tokenSecret == null)
                tokenSecret = string.Empty;

            if (this.Certificate.PrivateKey == null)
                throw new InvalidOperationException("The X509 Certificate does not containing a private key.");

            using (var hash = HashAlgorithm.Create("SHA1"))
            {
                var formatter = new RSAPKCS1SignatureFormatter((RSACryptoServiceProvider)Certificate.PrivateKey);
                formatter.SetHashAlgorithm("SHA1");

                var bytesToHash = Encoding.ASCII.GetBytes(signatureBaseString);
                var hashedBytes = hash.ComputeHash(bytesToHash);

                return Convert.ToBase64String(formatter.CreateSignature(hashedBytes));
            }
        }

        /// <summary>
        /// Gets the signature method name, always returns the "RSA-SHA1" string.
        /// </summary>
        public string SignatureMethod
        {
            get { return OAuthStrings.RSASHA1SignatureMethod; }
        }

        #endregion
    }

#endif
}